package net.softwarefabrik.bde.core.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import net.softwarefabrik.bde.core.model.BDEException;

/**
 * A container for common file operations.
 * 
 * @author Matthias Kuespert
 */
public final class FileUtil {

    /**
     * Private default constructor to prevent accidential creation.
     */
    private FileUtil() {
    }

    /**
     * Copies bytes from one stream to another.
     * 
     * Note: closes the streams when finished.
     * 
     * @param in The <code>InputStream</code> to read from.
     * @param out The <code>OutputStream</code> to write to.
     * @throws IOException Exceptions are handled by the caller.
     */
    public static void copy(InputStream in, OutputStream out) throws IOException {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = in.read(buffer)) >= 0) {
            out.write(buffer, 0, len);
        }
        in.close();
        out.close();
    }

    /**
     * Copy a file.
     * 
     * @param srcFile The source file.
     * @param destFile The destination file.
     * @throws IOException if an IO error occurs
     */
    public static void copyFile(File srcFile, File destFile) throws IOException {
        copy(new FileInputStream(srcFile), new FileOutputStream(destFile));
    }

    /**
     * Deletes a file. If the file is a directory the contents of the directory
     * are deleted first.
     * 
     * @param root The <code>File</code> to delete.
     * @throws BDEException if something goes wrong.
     */
    public static void delete(File root) throws BDEException {
        if (root.isDirectory()) {
            for (File f : root.listFiles()) {
                delete(f);
            }
        }
        if (!root.delete()) {
            throw new BDEException("Error deleting " + root.getAbsolutePath());
        }
    }

    /**
     * Emties a directory. All contents in the directory are deleted but the
     * directory itself is not touched.
     * 
     * @param root The directory to emty.
     * @throws BDEException if something goes wrong
     */
    public static void clearDirectory(File root) throws BDEException {
        for (File f : root.listFiles()) {
            delete(f);
        }
    }

    /**
     * Creates a file with the given content.
     * 
     * @param fileName The <code>File</code> to create.
     * @param content The content to write to the file.
     * @throws BDEException if something goes wrong
     */
    public static void writeFile(String fileName, String content) throws BDEException {
        try {
            FileWriter fw = new FileWriter(fileName);
            fw.write(content.toString());
            fw.close();
        } catch (IOException e) {
            throw new BDEException("Error writing to file " + fileName, e);
        }
    }

    /**
     * Downloads data from the given <code>URL</code> and writes it to a file
     * with the given name.
     * 
     * @param fileUrl The <code>URL</code> to download from.
     * @param fileName The name of the file in which to store the downloaded
     *        content.
     * @throws BDEException if something goes wrong
     */
    public static void downloadFile(String fileUrl, String fileName) throws BDEException {
        try {
            URL url = new URL(fileUrl);
            File of = new File(fileName);
            if (!of.getParentFile().exists()) {
                System.out.println("creating directory " + of.getParentFile().getAbsolutePath());
                if (!of.getParentFile().mkdirs()) {
                    throw new BDEException("Error: could not create parent directory for file " + fileName);
                }
            }
            FileOutputStream os = new FileOutputStream(fileName);
            copy(url.openStream(), os);
        } catch (MalformedURLException e) {
            throw new BDEException("Error creating URL from " + fileUrl, e);
        } catch (IOException e) {
            throw new BDEException("Error reading from URL " + fileUrl, e);
        }
    }
    
    public static boolean checkUrl(String fileUrl) {
        try {
            HttpURLConnection.setFollowRedirects(false);
            HttpURLConnection con = (HttpURLConnection) new URL(fileUrl).openConnection();
            con.setRequestMethod("HEAD");
            return (con.getResponseCode() == HttpURLConnection.HTTP_OK);
        } catch (Exception e) {
        }
        return false;
    }

    /**
     * Recursively searches a file starting in a given root-directory.
     * 
     * @param rootDir The directory in which the search should be started.
     * @param fileNamePattern The name or pattern of the file to look for.
     * @return The full path (including root) of the file.
     */
    public static String findFile(File root, String fileNamePattern) {

        if (root.isFile()) {
            if (root.getName().matches(fileNamePattern)) {
                return root.getAbsolutePath();
            }
        } else if (root.isDirectory()) {
            List<String> entries = new ArrayList<String>();
            for (String entry : root.list()) {
                entries.add(entry);
            }
            Collections.sort(entries);
            Collections.reverse(entries); // highest version first
            for (String entry : entries) {
                String file = findFile(new File(root.getAbsolutePath() + "/" + entry),
                                       fileNamePattern);
                if (null != file) {
                    return file;
                }
            }
        }
        return null;
    }
}
